//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

// open.c
//    Routines for opening, closing, creating  and deleting files
#include "ddk.h"
#include <stdio.h>
#include <time.h>
#include <ctype.h>

#include <misc.h>
#include <hash.h>
#include <fat.h>
#include <fatfile.h>

/*
* move_file()
*    Transfer the current file of a class CFatFile to the given node
*/
void FatDir::
move_file(class CFatFile *f, struct node *n)
{
    deref_node(f->f_node);
    f->f_node = n;
    /* new node already ref'ed on lookup */
    f->f_pos = 0;
}

/*
* fatfs_open()
*    Main entry for processing an open message
*/
int FatDir::
fatfs_open(class CFatFile *f, wchar_t *filename, int mode, int mask)
{
    struct node *n;
    int newfile = 0;

    /*
    * Have to be in dir to open down into a file
    */
    if (f->f_node->n_type != T_DIR) {
        return ENOTDIR;
    }
    /*
    * Check for permission.  Write/create needs ACC_WRITE,
    * changing permissions requires ACC_CHMOD.
    */
    if (((mode & (ACC_WRITE|ACC_CREATE|ACC_DIR)) &&
        !(f->f_perm & ACC_WRITE)) ||
        ((mode& ACC_CHMOD) && !(f->f_perm & ACC_CHMOD))) {
        return EPERM;
    }

    /*
    * Look up name
    */
    n = dir_look(f->f_node, filename);
    /*
    * Enforce FAT read-only bit
    */
    if (!IS_ERR(n)) {
        if (((n->n_type == T_DIR) && !(mode & ACC_DIR)) || //CRT fopen can't open a DIR
            ((n->n_type == T_FILE) && (mode & ACC_DIR)) || //CRT Bind extension can't bind a file
            (!(n->n_mode & ACC_WRITE) && (mode & O_TRUNC)) || //CRT can't TRUNC a ReadOnly NODE
            (!(n->n_mode & ACC_WRITE) && (mode & ACC_WRITE)) || //CRT can't write a ReadOnly NODE
            (!(n->n_mode & ACC_WRITE) && (mode & O_APPEND))) {//CRT can't append a Readonly NODE
            deref_node(n);
            return EPERM;
        }
    }

    if (n && (mode & O_TRUNC) && (n->n_mode & ACC_WRITE)) {
        if (pFat->clust_setlen(n->n_clust, 0)) {
            deref_node(n);
            return ENOSPC;
        }
        if ((mode & ACC_READONLY)) {
            struct directory *d;
            void *handle;
            d = get_dirent(f->f_node, n->n_slot, &handle);
            if (IS_ERR(d)) {
                deref_node(n);
                return PTR_ERR(d);
            }
            d->DIR_Attr |= DA_READONLY;
            n->n_mode &= ~ACC_WRITE;
            ddirty(handle);
            dfree(handle);
        }
        n->n_len = 0;
        n->n_flags |= N_DIRTY;
    }

    /*
     * No such file--do they want to create?
     */
    if (!n && !(mode & ACC_CREATE)) {
        return ESRCH;
    }

    if (!n && FatLock) {
        DPRINTF(("The Partition is Locked!\n"));
        return EPERM;
    }

    /*
     * If it's a new file, allocate the entry now.
     */
    if (!n) {
        n = dir_newfile(f, filename, mode);
        if (IS_ERR(n)) {
            return -1;
        }
        newfile = 1;
    }

    /*
     * If it's a symlink, don't let them open it
     * unless they have ACC_SYM specified
     */
//    if ((n->n_type == T_SYM) && !(mode & ACC_SYM)) {
//        deref_node(n);
//        return ESYMLINK;
//    }

    /*
     * If they want to use the existing file, set up the
     * node and let them go for it.  Note that this case
     * MUST be !newfile, or it would have been caught above.
     */
    if (!(mode & ACC_CREATE)) {
        if (!newfile && (mode & ACC_WRITE)) {
            /*
             * When an existing file is opened for modification,
             * mark its new modification date/time.  This makes
             * commands like "touch" work.
             * this is a bug, when you use access ,this is a error
             */
            // dir_timestamp(n, 0);
            n->n_opmode |= ACC_WRITE;
        }

        goto success;
    }

    //_O_CREAT | _O_EXCL if file exists,then return fail
    if (!newfile && (mode & ACC_FAILIFEXIST)) {
        deref_node(n);
        return EEXIST;
    }
    /*
     * Creation is desired.  If there's an existing file, free
     * its storage.
     */
    if (!newfile && !(mode & ACC_APPEND)) {
        /*
         * Can't rewrite a directory like this
         */
        if (n->n_type == T_DIR) {
            deref_node(n);
            return EEXIST;
        }
    }
success:
    move_file(f, n);

    return 0;
}

/*
 * fatfs_close()
 *    Do closing actions on a file
 */
void FatDir::
fatfs_close(class CFatFile *f)
{
    if (f->f_rename_id) {
        cancel_rename(f);
    }

    deref_node(f->f_node);
}

/*
 * fatfs_remove()
 *    Remove an entry in the current directory
 */
int FatDir::
 fatfs_remove(class CFatFile *f, struct node *pNode)
{
    /*
     * Have to have write permission
     */
    if (!(f->f_perm & ACC_WRITE)) {
        return EPERM;
    }

    /*
     * Have to be in a directory
     */
    if (f->f_node->n_type != T_DIR) {
        return EINVAL;
    }

    /*
     * Make sure parameter is valid
     */
    if (NULL == pNode) {
        return EINVAL;
    }

    /*
    *Read only protect bit
    */
    if (!(pNode->n_mode & ACC_WRITE)) {//CAN'T Remove a Read Only Node!!!
        return EPERM;
    }

    /*
     * We must be only access
     */
    if (pNode->n_refs > 1) {
        /*
         * Try unhashing if it might be the only other reference
         */
        if (pNode->n_flags & N_FID) {
            /*
             * Release our ref and tell the requestor he
             * might want to try again.
             */
            pNode->n_flags &= ~N_FID;
            return EAGAIN;
        }

        return EBUSY;
    }

    /*
     * Directories--only allowed when empty
     */
    if (pNode->n_type == T_DIR) {
        if (!dir_empty(pNode)) {
            return EBUSY;
        }
    }

    /*
     * Ask dir routines to remove
     */
    dir_remove(pNode);

    /*
     * Throw away his storage, mark him as already cleaned
     */
    pFat->clust_setlen(pNode->n_clust, 0L);
    pNode->n_flags |= N_DEL;

    f->f_node->n_flags |= N_DIRTY;

    /*
     * Return success
     */
    return 0;
}

/*
 * fatfs_remove()
 *    Remove an entry in the current directory
 */
int FatDir::
fatfs_remove(class CFatFile *f, wchar_t *filename)
{
    struct node *n;

    /*
     * Have to have write permission
     */
    if (!(f->f_perm & ACC_WRITE)) {
        return EPERM;
    }

    /*
     * Have to be in a directory
     */
    if (f->f_node->n_type != T_DIR) {
        return EINVAL;
    }

    /*
     * Look up entry.  Fail if no such file.
     */
    n = dir_look(f->f_node, filename);
    if (IS_ERR(n)) {
        return PTR_ERR(n);
    }

    /*
    *Read only protect bit
    */
    if (!(n->n_mode & ACC_WRITE)) {//CAN'T Remove a Read Only Node!!!
        deref_node(n);
        return EPERM;
    }

    /*
     * We must be only access
     */
    if (n->n_refs > 1) {
        /*
         * Try unhashing if it might be the only other reference
         */
        if (n->n_flags & N_FID) {
            /*
             * Release our ref and tell the requestor he
             * might want to try again.
             */
            n->n_flags &= ~N_FID;
            deref_node(n);
            return EAGAIN;
        }

        deref_node(n);
        return EBUSY;
    }

    /*
     * Directories--only allowed when empty
     */
    if (n->n_type == T_DIR) {
        if (!dir_empty(n)) {
            deref_node(n);
            return EBUSY;
        }
    }

    /*
     * Ask dir routines to remove
     */
    dir_remove(n);

    /*
     * Throw away his storage, mark him as already cleaned
     */
    pFat->clust_setlen(n->n_clust, 0L);
    n->n_flags |= N_DEL;

    f->f_node->n_flags |= N_DIRTY;

    deref_node(n);
    /*
     * Done with node
     */
    sync();

    /*
     * Return success
     */
    return 0;
}

/*
 * fatfs_seek()
 *    Set file position
 */
int FatDir::
fatfs_seek(class CFatFile *f, UInt32 pos)
{
    if (pos < 0) {
        return EINVAL;
    }
    f->f_pos = pos;

    return 0;
}

/*
* fatfs_lock()
*    Lock the partition
*/
void FatDir::
fatfs_lock(void)
{
    FatLock = 1;
}

/*
* fatfs_unlock()
*    Unock the partition
*/
void FatDir::
fatfs_unlock(void)
{
    FatLock = 0;
}

/*
* fatfs_islock()
*    If the partition is lock
*/
int FatDir::
fatfs_islock(void)
{
    return FatLock;
}
